package dcedit.factories;

// Import Java SDK Classes
import java.util.*;
import java.text.*;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.LineNumberReader;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;

// Import Codec Classes
import org.apache.commons.codec.binary.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.*;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;

// Import Substance Classes
import org.pushingpixels.substance.api.SubstanceLookAndFeel;


// Import Application Classes
import dcedit.DCEdit;
import dcedit.cha.*;
import dcedit.util.*;

/**
 * ActionFactory class<br/>
 * This factory class defines all swing listeners used by the application.<br/>
 * This class contains only class methods and returns new instances of each listener.
 * @author dca
 */
public class ActionFactory {
   /** Private Constructor */
   private ActionFactory() {} // constructor

   /**
    * createWindowActionListener method<br/>
    * This method defines and returns a new instance of a window adapter to handle window events.<br/>
    * This method will save the current font configuration and color configuration before closing.
    * @return WindowAdapter The object which handles window events
    * @see Util#closeAllTabs()
    * @see Util#writeDCEditFontConfig()
    * @see Util#writeDCEditColorConfig()
    * @see DCEdit#main()
    */
   public static WindowAdapter createWindowActionListener() {
      return new WindowAdapter() {
         public void windowClosed(WindowEvent we) {}
         public void windowClosing(WindowEvent we) {
            final int STATUS = Util.closeAllTabs();
            Util.writeDCEditFontConfig();
            Util.writeDCEditColorConfig();

            if(STATUS != JOptionPane.CANCEL_OPTION) {
               System.exit(0);
            } // END if
         } // END windowClosing
      };
   } // END createWindowActionListener

   /**
    * createExitActionListener method<br/>
    * This method defines and returns a new action listener which handles application exit events.<br/>
    * This method will save the current font configuration and color configuration before exiting.
    * @return ActionListener The object which handles application close events
    * @see Util#closeAllTabs()
    * @see Util#writeDCEditFontConfig()
    * @see Util#writeDCEditColorConfig()
    * @see MenuFactory#createFileMenu(DCEdit)
    */
   public static ActionListener createExitActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            final int STATUS = Util.closeAllTabs();
            Util.writeDCEditFontConfig();
            Util.writeDCEditColorConfig();

            if(STATUS != JOptionPane.CANCEL_OPTION) {
               System.exit(0);
            } // END if
         } // END actionPerformed
      };
   } // END createExitActionListener

   /**
    * createNewTabActionListener method<br/>
    * This method defines and returns a new action listener which handles adding a new tab into the tab pane.
    * @return ActionListener The object which handles new tab events
    * @see Util#addNewTab()
    * @see MenuFactory#createFileMenu(DCEdit)
    */
   public static ActionListener createNewTabActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            Util.addNewTab();
         } // END actionPerformed
      };
   } // END createNewTabActionListener

   /**
    * createOpenTabActionListener method<br/>
    * This method defines and returns a new action listener which handles the opening of documents into the tab pane as new tab items.<br/>
    * When a user selects &quot;Open&quot; from the &quot;File&quot; menu, this method is invoked.<br/>
    * The file chooser is shown.  The user may select a file.<br/>
    * If the user selects a file, the contents of that file are opened into a new tab.
    * @return ActionListener The object which handles open tab events
    * @see Util#addNewTab(File)
    * @see MenuFactory#createFileMenu(DCEdit)
    */
   public static ActionListener createOpenTabActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            Constants.FILE_CHOOSER.setFileSelectionMode(JFileChooser.FILES_ONLY);
            final int OPTION = Constants.FILE_CHOOSER.showOpenDialog((Component)ae.getSource());

            // Only proceed if the user approved a file.
            if(OPTION == JFileChooser.APPROVE_OPTION) {
               Util.addNewTab(Constants.FILE_CHOOSER.getSelectedFile());
            } // END if
         } // END actionPerformed
      };
   } // END createOpenTabActionListener

   /**
    * createTabPaneLanguageActionListener method<br/>
    * This method defines and returns a new action listener which handles the selection of code languages.<br/>
    * When a user selects a new code language from the combo-box, the syntax highlighting for all text panes are updated.
    * @return ActionListener The object which handles code language combo-box selections
    * @see Util#getTextPane(int)
    * @see DCTextEditor#setSyntaxStyle(dcedit.util.DCTextEditor.SyntaxStyle)
    * @see ComboBoxFactory#createLanguageList()
    */
   public static ActionListener createTabPaneLanguageActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            try {
               // For each text pane
               for(int ix = 0 ; ix < Constants.TAB_PANE.getTabCount() ; ++ix) {
                  Util.getTextPane(ix).setSyntaxStyle((DCTextEditor.SyntaxStyle)Constants.LANGUAGE_LIST.getSelectedItem());
               } // END loop
            } catch(Exception e) {
               System.out.println(e.getMessage());
            } // END try/catch
         } // END actionPerformed
      };
   } // END createTabPaneLanguageActionListener

   /**
    * createTabPaneFontActionListener method<br/>
    * This method defines and returns a new action listener which handles font face.<br/>
    * When a user selects bold or italic, or selects a new font name or size, the font face is updated for all text panes.
    * This listener is used by the bold, italic, font name, and font size combo-boxes.
    * @return ActionListener The object which handles font combo-box selections
    * @see Util#createCurrentFont(DCTextEditor)
    * @see Util#getTextPane(int)
    * @see BtnFactory#createBoldBtn()
    * @see BtnFactory#createItalicBtn()
    * @see ComboBoxFactory#createFontNameList()
    * @see ComboBoxFactory#createFontSizeList()
    */
   public static ActionListener createTabPaneFontActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            try {
               // For each text pane
               for(int ix = 0 ; ix < Constants.TAB_PANE.getTabCount() ; ++ix) {
                  Util.createCurrentFont(Util.getTextPane(ix));
               } // END loop
            } catch(Exception e) {
               System.out.println(e.getMessage());
            } // END try/catch
         } // END actionPerformed
      };
   } // END createTabPaneFontActionListener

   /**
    * createAutoIndentActionListener method<br/>
    * This method defines and returns a new action listener which handles code auto indentation.<br/>
    * When a user selects auto indentation, it is applied to all text panes.
    * Auto indentation tells a text pane to insert whitespace, matching the preceding line,
    * whenever the user inserts a carriage return.
    * @return ActionListener The object which handles code auto-indentation
    * @see Util#getTextPane(int)
    * @see DCTextEditor#setAutoIndentEnabled(boolean)
    * @see BtnFactory#createAutoIndentBtn()
    */
   public static ActionListener createAutoIndentActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            try {
               // For each text pane
               for(int ix = 0 ; ix < Constants.TAB_PANE.getTabCount() ; ++ix) {
                  Util.getTextPane(ix).setAutoIndentEnabled(Constants.AUTO_INDENT_BTN.isSelected());
               } // END loop
            } catch(Exception e) {
               System.out.println(e.getMessage());
            } // END try/catch
         } // END actionPerformed
      };
   } // END createAutoIndentActionListener

   /**
    * createAddTabListener method<br/>
    * This method defines and returns a new mouse listener which handles the insertion of tabs into the tab pane.<br/>
    * When a user clicks the plus icon in the application, this method is invoked,<br/>
    * and a new tab is inserted into the tab pane.
    * @return MouseAdapter The object which handles the insertion of tabs into the tab pane
    * @see Util#addNewTab()
    * @see BtnFactory#createAddBtn()
    */
   public static MouseAdapter createAddTabListener() {
      return new MouseAdapter() {
         public void mouseEntered(MouseEvent me) {
            me.getComponent().setCursor(new Cursor(Cursor.HAND_CURSOR));
         } // END mouseEntered

         public void mouseExited(MouseEvent me) {
            me.getComponent().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
         } // END mouseExited

         public void mouseClicked(MouseEvent me) {
            Util.addNewTab();
         } // END mouseClicked
      };
   } // END createAddTabListener

   /**
    * createOpenTabListener method<br/>
    * This method defines and returns a new mouse listener which handles the opening of documents into the tab pane.<br/>
    * When a user clicks the folder icon in the application, this method is invoked.<br/>
    * The file chooser is shown.  The user may select a file.<br/>
    * If the user selects a file, the contents of that file are opened into a new tab.
    * @return MouseAdapter The object which handles the opening of documents, as new tabs, into the tab pane
    * @see Util#addNewTab(File)
    * @see BtnFactory#createOpenBtn()
    */
   public static MouseAdapter createOpenTabListener() {
      return new MouseAdapter() {
         public void mouseEntered(MouseEvent me) {
            me.getComponent().setCursor(new Cursor(Cursor.HAND_CURSOR));
         } // END mouseEntered

         public void mouseExited(MouseEvent me) {
            me.getComponent().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
         } // END mouseExited

         public void mouseClicked(MouseEvent me) {
            Constants.FILE_CHOOSER.setFileSelectionMode(JFileChooser.FILES_ONLY);
            final int OPTION = Constants.FILE_CHOOSER.showOpenDialog(me.getComponent());

            // Only proceed if the user approved a file.
            if(OPTION == JFileChooser.APPROVE_OPTION) {
               Util.addNewTab(Constants.FILE_CHOOSER.getSelectedFile());
            } // END if
         } // END mouseClicked
      };
   } // END createOpenTabListener

   /**
    * createSaveTabListener method<br/>
    * This method defines and returns a new mouse listener which handles the saving of documents to the file system.<br/>
    * When a user clicks the floppy disc icon in the application, this method is invoked.<br/>
    * If the file already exists on the file system, the document is saved to that file,<br/>
    * otherwise, the file chooser is shown for the user to select a file location.
    * @return MouseAdapter The object which handles the saving of documents to the file system
    * @see Util#saveOrSaveAs(int)
    * @see Util#getSelectedIndex()
    * @see BtnFactory#createSaveBtn()
    */
   public static MouseAdapter createSaveTabListener() {
      return new MouseAdapter() {
         public void mouseEntered(MouseEvent me) {
            me.getComponent().setCursor(new Cursor(Cursor.HAND_CURSOR));
         } // END mouseEntered

         public void mouseExited(MouseEvent me) {
            me.getComponent().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
         } // END mouseExited

         public void mouseClicked(MouseEvent me) {
            Util.saveOrSaveAs(Util.getSelectedIndex());
         } // END mouseClicked
      };
   } // END createSaveTabListener

   /**
    * createDeleteTabListener method<br/>
    * This method defines and returns a new mouse listener which handles the deletion of tabs from the tab pane.<br/>
    * When a user clicks the x icon in the application, this method is invoked.<br/>
    * If the document has not been saved, the user is given the option to save the document first.<br/>
    * If the user selects &quot;Yes&quot; then the file chooser is shown, allowing the user to select a file location.<br/>
    * If it is the last tab in the tab pane which is being deleted, then a new, empty tab will be included after deletion,<br/>
    * this ensuring that the tab pane is never left empty.
    * @return MouseAdapter The object which handles the deletion of tabs from the tab pane
    * @see Util#getSelectedTextPane()
    * @see Util#saveOrSaveAs(int)
    * @see Util#getSelectedIndex()
    * @see Util#addNewTab()
    * @see BtnFactory#createDeleteBtn()
    */
   public static MouseAdapter createDeleteTabListener() {
      return new MouseAdapter() {
         public void mouseEntered(MouseEvent me) {
            me.getComponent().setCursor(new Cursor(Cursor.HAND_CURSOR));
         } // END mouseEntered

         public void mouseExited(MouseEvent me) {
            me.getComponent().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
         } // END mouseExited

         public void mouseClicked(MouseEvent me) {
            try {
               if(Constants.TAB_PANE.getTabCount() > 0) {
                  // Get the currently select text pane which we will delete.
                  final DCTextEditor SELECTED_TEXT_PANE = Util.getSelectedTextPane();

                  // Check if there are pending edits on the selected pane.
                  if(!SELECTED_TEXT_PANE.isEmpty() && SELECTED_TEXT_PANE.isDirty()) {
                     // Prompt the user to save.
                     final int OPTION = JOptionPane.showConfirmDialog(me.getComponent(), Constants.DIRTY_SAVE_MESSAGE_TEXT);

                     if(OPTION == JOptionPane.CANCEL_OPTION) {
                        // Don't proceed if the user selected cancel.
                        return;
                     } else if(OPTION == JOptionPane.YES_OPTION) {
                        // Only save if user selected yes.
                        Util.saveOrSaveAs(Util.getSelectedIndex());
                     } // END if/else if
                  } // END if

                  // Finally, remove the pane.
                  Constants.TAB_PANE.remove(Util.getSelectedIndex());
               } // END if
               if(Constants.TAB_PANE.getTabCount() == 0) {
                  Util.addNewTab();
               } // END if
            } catch(Exception e) {
               System.out.println(e.getMessage());
            } // END try/catch
         } // END mouseClicked
      };
   } // END createDeleteTabListener

   /**
    * createTabPaneListener method<br/>
    * This method defines and returns a new mouse listener which handles the display of the tab pane context menu.<br/>
    * The context menu is shown when the user right-clicks inside on a tab.
    * @return MouseAdapter The object which handles display of the tab pane context menu
    * @see MenuFactory#createTabPaneContextMenu()
    * @see PanelFactory#createTabbedPane()
    */
   public static MouseAdapter createTabPaneListener() {
      return new MouseAdapter() {
         final JPopupMenu CONTEXT_MENU = MenuFactory.createTabPaneContextMenu();

         public void mouseClicked(MouseEvent me) {
            if(SwingUtilities.isRightMouseButton(me)) {
               CONTEXT_MENU.show(Constants.TAB_PANE, me.getComponent().getX(), me.getComponent().getY());
            } // END if
         } // END mouseClicked
      };
   } // END createTabPaneListener

   /**
    * createCloseActionListener method<br/>
    * This method defines and returns a new mouse listener which handles the deletion of tabs from the tab pane.<br/>
    * When a user clicks &quot;Close&quot; from either the &quot;File&quot; menu or the context menu, this method is invoked.<br/>
    * If the document has not been saved, the user is given the option to save the document first.<br/>
    * If the user selects &quot;Yes&quot; then the file chooser is shown, allowing the user to select a file location.<br/>
    * If it is the last tab in the tab pane which is being deleted, then a new, empty tab will be included after deletion,
    * @return ActionListener The object which handles the deletion of tabs from the tab pane
    * @see Util#getSelectedTextPane()
    * @see Util#saveOrSaveAs(int)
    * @see Util#getSelectedIndex()
    * @see Util#addNewTab()
    * @see MenuFactory#createFileMenu(DCEdit)
    * @see MenuFactory#createTabPaneContextMenu()
    */
   public static ActionListener createCloseActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            try {
               if(Constants.TAB_PANE.getTabCount() > 0) {
                  // Get the currently selected text pane which we will close.
                  final DCTextEditor SELECTED_TEXT_PANE = Util.getSelectedTextPane();

                  // Check if there are pending edits on the selected pane.
                  if(!SELECTED_TEXT_PANE.isEmpty() && SELECTED_TEXT_PANE.isDirty()) {
                     // Prompt the user to save.
                     final int OPTION = JOptionPane.showConfirmDialog((Component)ae.getSource(), Constants.DIRTY_SAVE_MESSAGE_TEXT);

                     if(OPTION == JOptionPane.CANCEL_OPTION) {
                        // Don't proceed if the user selected cancel.
                        return;
                     } else if(OPTION == JOptionPane.YES_OPTION) {
                        // Only save if user selected yes.
                        Util.saveOrSaveAs(Util.getSelectedIndex());
                     } // END if/else
                  } // END if

                  // Finally, remove the pane.
                  Constants.TAB_PANE.remove(Util.getSelectedIndex());
               } // END if
               if(Constants.TAB_PANE.getTabCount() == 0) {
                  Util.addNewTab();
               } // END if
            } catch(Exception e) {
               System.out.println(e.getMessage());
            } // END try/catch
         } // END actionPerformed
      };
   } // END createCloseActionListener

   /**
    * createCloseAllActionListener method<br/>
    * This method defines and returns a new action listener which handles the deletion of all tabs from the tab pane.<br/>
    * When a user clicks &quot;Close All&quot; from either the &quot;File&quot; menu or the context menu, this method is invoked.<br/>
    * All tabs will be closed, prompting the user to save if there are pending edits.<br/>
    * If it is the last tab in the tab pane which is being deleted, then a new, empty tab will be included after deletion,
    * @return ActionListener The object which handles the deletion of all tabs from the tab pane
    * @see Util#closeAllTabs()
    * @see Util#addNewTab()
    * @see MenuFactory#createFileMenu(DCEdit)
    * @see MenuFactory#createTabPaneContextMenu()
    */
   public static ActionListener createCloseAllActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Closes all tabs.
            Util.closeAllTabs();

            // Finally, add a new tab.
            Util.addNewTab();
         } // END actionPerformed
      };
   } // END createCloseAllActionListener

   /**
    * createCloseOthersActionListener method<br/>
    * This method defines and returns a new action listener which handles the deletion of all but the currently selected tab.<br/>
    * When the user clicks &quot;Close Others&quot; from either the &quot;File&quot; menu or the context menu, this method is invoked.<br/>
    * All but the currently selected tab will be deleted, prompting the user to save if there are pending edits.
    * @return ActionListener The object which handles the closing of all but the currently selected tab
    * @see MenuFactory#createFileMenu(DCEdit)
    * @see MenuFactory#createTabPaneContextMenu()
    */
   public static ActionListener createCloseOthersActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            while(Constants.TAB_PANE.getTabCount() > 1) {
               try {
                  // Get the currently selected text pane so that we DON'T delete it
                  final int ORIGINAL_SELECTED_INDEX = Constants.TAB_PANE.getSelectedIndex();
                  final int OTHER_INDEX = (Constants.TAB_PANE.getSelectedIndex() == 0 ? 1 : 0);
                  final DCTextEditor OTHER_TEXT_PANE = Util.getTextPane(OTHER_INDEX);

                  // Check if there are pending edits on the selected pane.
                  if(!OTHER_TEXT_PANE.isEmpty() && OTHER_TEXT_PANE.isDirty()) {
                     // Since we are going to prompt the user, show them the pane which we are prompting about.
                     Constants.TAB_PANE.setSelectedIndex(OTHER_INDEX);

                     // Prompt the user to save.
                     final int OPTION = JOptionPane.showConfirmDialog((Component)ae.getSource(), Constants.DIRTY_SAVE_MESSAGE_TEXT);

                     if(OPTION == JOptionPane.CANCEL_OPTION) {
                        // Don't proceed if the user selected cancel.
                        return;
                     } else if(OPTION == JOptionPane.YES_OPTION) {
                        // Only save if user selected yes.
                        Util.saveOrSaveAs(Util.getSelectedIndex());
                     } // END if/else

                     // In order to keep track of the pane the user wanted to keep from closing,
                     // set the selected index back to the original index.
                     Constants.TAB_PANE.setSelectedIndex(ORIGINAL_SELECTED_INDEX);
                  } // END if

                  // Finally, remove the pane.
                  Constants.TAB_PANE.remove(Constants.TAB_PANE.getSelectedIndex() == 0 ? 1 : 0);
               } catch(Exception e) {
                  System.out.println(e.getMessage());
               } // END try/catch
            } // END loop
         } // END actionPerformed
      };
   } // END createCloseOthersActionListener

   /**
    * createSaveActionListener method<br/>
    * This method defines and returns a new action listener which handles the saving of documents to the file system.<br/>
    * When the user clicks &quot;Save&quot; from either the &quot;File&quot; menu or the context menu, this method is invoked.<br/>
    * If the file already exists on the file system, the document is saved to that file,<br/>
    * otherwise, the file chooser is shown for the user to select a file location.
    * @return ActionListener The object which handles the saving of documents to the file system
    * @see Util#saveOrSaveAs(int)
    * @see Util#getSelectedIndex()
    * @see MenuFactory#createFileMenu(DCEdit)
    * @see MenuFactory#createTabPaneContextMenu()
    */
   public static ActionListener createSaveActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            Util.saveOrSaveAs(Util.getSelectedIndex());
         } // END actionPerformed
      };
   } // END createSaveActionListener

   /**
    * createSaveAsActionListener method<br/>
    * This method defines and returns a new action listener which handles the saving of documents to the file system.<br/>
    * When the user clicks &quot;Save As&quot; from either the &quot;File&quot; menu or the context menu, this method is invoked.<br/>
    * The file chooser is shown for the user to select a file location.
    * @return ActionListener The object which handles the saving of documents to the file system
    * @see MenuFactory#createFileMenu(DCEdit)
    * @see MenuFactory#createTabPaneContextMenu()
    */
   public static ActionListener createSaveAsActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Display the file chooser which will allow the user to select a save location.
            Constants.FILE_CHOOSER.setFileSelectionMode(JFileChooser.FILES_ONLY);
            final int OPTION = Constants.FILE_CHOOSER.showSaveDialog(Util.getSelectedTextPane());

        	// Only proceed if user confirmed the save.
            if(OPTION == JFileChooser.APPROVE_OPTION) {
               try {
                  // Get the chosen file location.
                  final File SELECTED_FILE = Constants.FILE_CHOOSER.getSelectedFile();

                  // Associate this file location with the currently selected text pane.
                  Util.getSelectedTextPane().saveAs(SELECTED_FILE);

                  // Update the tab name to be the same as the file name.
                  Util.setSelectedTabText(SELECTED_FILE.getName());
               } catch(Exception e) {
                  System.out.println(e.getMessage());
               } // END try/catch
            } // END if
         } // END actionPerformed
      };
   } // END createSaveAsActionListener

   public static ActionListener createSaveAsTemplateActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Prompt for template name.
            String templateName = JOptionPane.showInputDialog(null, "Please name this template", "Template Name", JOptionPane.QUESTION_MESSAGE);
            templateName = FilenameUtils.getBaseName(templateName); // In case the user added an extension or path.

            // Check if there's already a template with that name.
            for(final File FILE : Util.getDCEditCustomTemplates()) {
               final String FILE_NAME = FilenameUtils.getBaseName(FILE.getAbsolutePath());
               if(FILE_NAME.equals(templateName)) {
                  // Offer to overwrite the file.
                  final int CHOICE = JOptionPane.showConfirmDialog(null, "There is already a template with that name.  Do you want to overwrite it?", "Template Name Error", JOptionPane.YES_NO_OPTION);
                  if(JOptionPane.YES_OPTION != CHOICE) {
                	  return; // If NO, then stop doing this action.
                  } // END if
               } // END if
            } // END loop

            try {
               // Save template to <user_home>/.dcedit/templates
               final File NEW_FILE = new File(Util.ensureDCEditConfigLocation(Constants.TEMPLATES_PATH), templateName + Constants.TEMPLATE_SUFFIX);

               // Associate this file location with the currently selected text pane.
               Util.getSelectedTextPane().saveAs(NEW_FILE);

               // Update the tab name to be the same as the file name.
               Util.setSelectedTabText(NEW_FILE.getName());

               // Update Custom menu with new template.
               // TODO: Not sure if this is possible.
            } catch(Exception e) {
               System.out.println(e.getMessage());
            } // END try/catch
         } // END actionPerformed
      };
   } // END createSaveAsTemplateActionListener

   /**
    * createCloseDiceDialogActionListener method<br/>
    * This method defines and returns a new action listener which handles the closing of dialogs.<br/>
    * Most dialogs in the application have a close/cancel button.<br/>
    * They invoke this method.
    * @param DIALOG The dialog to be closed
    * @return ActionListener The object which handles the closing of dialogs
    */
   public static ActionListener createCloseDialogActionListener(final JDialog DIALOG) {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // QA check to make sure the dialog parameter is valid.
            if(null != DIALOG) {
               breakSearch = true;
               DIALOG.setVisible(false);
            } // END if
         } // END actionPerformed
      };
   } // END createCloseDiceDialogActionListener

   /**
    * createDiceModOptionActionListener method<br/>
    * This method defines and returns a new action listener which handles the update of dice mod dialog controls.<br/>
    * This listener is used by the dice mod option list combo box.
    * @return ActionListener The object which handles the update of dice mod dialog controls
    * @see Util#updateDiceModeControls()
    * @see Constants#DICE_MOD_OPTIONS_LIST
    */
   public static ActionListener createDiceModOptionActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            Util.updateDiceModeControls();
         } // END actionPerformed
      };
   } // END createDiceModOptionActionListener

   /**
    * createDiceModOptionActionListener method<br/>
    * This method defines and returns a new change listener which handles the update of dice mod dialog controls.<br/>
    * This listener is used by all dice mod spinners.
    * @return ChangeListener The object which handles the update of dice mod dialog controls
    * @see Util#updateDiceModeControls()
    * @see Constants#DICE_QTY_SPINNER
    * @see Constants#DICE_MOD_SPINNER
    * @see Constants#DICE_NUM_SIDES_SPINNER
    */
   public static ChangeListener createDiceModSpinnerChangeListener() {
      return new ChangeListener() {
         public void stateChanged(ChangeEvent ce) {
            Util.updateDiceModeControls();
         } // END stateChanged
      };
   } // END createDiceModSpinnerChangeListener

   /**
    * createTranslateTemplateActionListener method<br/>
    * This method defines and returns a new action listener which creates and displays the translate template dialog.<br/>
    * When the user clicks &quot;Translate Template&quot; from the tab context menu, this method is invoked.<br/>
    * The translate template dialog translates string tokens within a text pane into different strings.<br/>
    * The resulting text is written to a new tab pane.
    * @return ActionListener The object which handles the creation and display of the translate template dialog.
    * @see Util#getSelectedTextPane()
    * @see DialogFactory#createTranslateTemplateDialog(String, boolean)
    * @see MenuFactory#createTabPaneContextMenu()
    */
   public static ActionListener createTranslateTemplateActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            final String TEXT = Util.getSelectedTextPane().getText();
            final boolean NEW_TAB = true;
            final JDialog DIALOG = DialogFactory.createTranslateTemplateDialog(TEXT,NEW_TAB);
            DIALOG.setVisible(true);
         } // END actionPerformed
      };
   } // END createTranslateTemplateActionListener

   /**
    * createTabPaneMouseWheelListener method<br/>
    * This method defines and returns a new mouse wheel listener which checks for key mask characters during scrolling.<br/>
    * When the user holds down the ALT key while scrolling, the font name list is cycled.<br/>
    * When the user holds down the CTRL key while scrolling, the font size list is cycled.<br/>
    * When the user holds down any other key while scrolling, or the user just scrolls, then the text pane will scroll through its content.
    * @return MouseWheelListener The object which handles cycling through font names and sizes, and also text panes
    * @see DCTextEditor
    * @see Util#addNewTab(String)
    */
   public static MouseWheelListener createTabPaneMouseWheelListener() {
      return new MouseWheelListener() {
         public void mouseWheelMoved(MouseWheelEvent mwe) {
            int scrollAmt = mwe.getWheelRotation();

            // If ALT key is held
            if(mwe.getModifiers() == MouseWheelEvent.ALT_MASK) {
               // Cycle through font names
               int currentIndex = Constants.FONT_NAME_LIST.getSelectedIndex();
               int itemCount = Constants.FONT_NAME_LIST.getItemCount();

               // Check scroll direction and font size bounds
               if(scrollAmt < 0 && currentIndex < itemCount - 1) {
                  // Scrolling up
                  Constants.FONT_NAME_LIST.setSelectedIndex(currentIndex + 1);
               } else if(scrollAmt > 0 && currentIndex > 0) {
                  // Scrolling down
                  Constants.FONT_NAME_LIST.setSelectedIndex(currentIndex - 1);
               } // END if/else rotate UP/DOWN
            // If CONTROL key is held
            } else if(mwe.getModifiers() == MouseWheelEvent.CTRL_MASK) {
               // Cycle through font sizes
               int currentIndex = Constants.FONT_SIZE_LIST.getSelectedIndex();
               int itemCount = Constants.FONT_SIZE_LIST.getItemCount();

               // Check scroll direction and font size bounds
               if(scrollAmt < 0 && currentIndex < itemCount - 1) {
                  // Scrolling up
                  Constants.FONT_SIZE_LIST.setSelectedIndex(currentIndex + 1);
               } else if(scrollAmt > 0 && currentIndex > 0) {
                  // Scrolling down
                  Constants.FONT_SIZE_LIST.setSelectedIndex(currentIndex - 1);
               } // END if/else rotate UP/DOWN
            } else {
               // No mask key was used, just scroll the text pane
               DCTextEditor scrollPane = (DCTextEditor)mwe.getSource();
               JScrollBar scrollBar = scrollPane.getVerticalScrollBar();
               scrollBar.setValue(scrollBar.getValue() + scrollAmt);
            } // END if/else ALT/CTRL
         } // END mouseWheelMoved
      };
   } // END createTabPaneMouseWheelListener

   /**
    * createTabFontColorActionListener method<br/>
    * This method defines and returns a new action listener which updates the text pane foreground color for all new and existing text panes.<br/>
    * When the user clicks the &quot;FG Color&quot; button, this method is invoked.<br/>
    * The color chooser is shown, which the user can use to select a new foreground color for text panes.
    * @return ActionListener The object which handles the setting of text pane foreground colors
    * @see Colors#getTabForegroundColor()
    * @see Colors#setTabForegroundColor(Color)
    * @see BtnFactory#createFontColorBtn()
    */
   public static ActionListener createTabFontColorActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Show the color chooser so that the user can select a new color.
            Color color = JColorChooser.showDialog((Component)ae.getSource(),
                                                   Constants.FONT_COLOR_TEXT,
                                                   Colors.getTabForegroundColor());

            // Only proceed if a color was selected.
            if(null != color) {
               // Register the text pane foreground color to the newly selected color.
               Colors.setTabForegroundColor(color);

               // Loop through all existing text panes.
               for(int tabIndex = 0 ; tabIndex < Constants.TAB_PANE.getTabCount() ; ++tabIndex) {
                  // Get the text pane.
                  final DCTextEditor TEXT_PANE = Util.getTextPane(tabIndex);

                  // Set the text pane foreground color to the newly selected color.
                  TEXT_PANE.setFG(color);
               } // for each tab

               // Updated the foreground color button's background to the newly selected color.
               Constants.FONT_COLOR_BTN.setBackground(Colors.getTabForegroundColor());
            } // END if a color was selected
         } // END actionPerformed
      };
   } // END createTabFontColorActionListener

   /**
    * createTabBackgroundColorActionListener method<br/>
    * This method defines and returns a new action listener which updates the text pane background color for all new and existing text panes.<br/>
    * When the user clicks the &quot;BG Color&quot; button, this method is invoked.<br/>
    * The color chooser is shown, which the user can use to select a new background color for text panes.
    * @return ActionListener The object which handles the setting of text pane background colors
    * @see Colors#getTabBackgroundColor()
    * @see Colors#setTabBackgroundColor(Color)
    * @see BtnFactory#createBackgroundColorBtn()
    */
   public static ActionListener createTabBackgroundColorActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Show the color chooser so that the user can select a new color.
            Color color = JColorChooser.showDialog((Component)ae.getSource(),
                                                   Constants.BACKGROUND_COLOR_TEXT,
                                                   Colors.getTabBackgroundColor());

            // Only proceed if a color was selected.
            if(null != color) {
               // Register the text pane foreground color to the newly selected color.
               Colors.setTabBackgroundColor(color);

               // Loop through all existing text panes.
               for(int tabIndex = 0 ; tabIndex < Constants.TAB_PANE.getTabCount() ; ++tabIndex) {
                  // Get the text pane.
                  final DCTextEditor TEXT_PANE = Util.getTextPane(tabIndex);

                  // Set the text pane foreground color to the newly selected color.
                  TEXT_PANE.setBG(color);
               } // for each tab

               // Updated the background color button's background to the newly selected color.
               Constants.BACKGROUND_COLOR_BTN.setBackground(Colors.getTabBackgroundColor());
            } // END if a color was selected
         } // END actionPerformed
      };
   } // END createTabBackgroundColorActionListener

   /**
    * createTabHighlightColorActionListener method<br/>
    * This method defines and returns a new action listener which updates the line highlight color for all new and existing text panes.<br/>
    * When the user clicks the &quot;HL Color&quot; button, this method is invoked.<br/>
    * The color chooser is shown, which the user can use to select a new line highlight color for text panes.
    * @return ActionListener The object which handles the setting of text pane line highlight colors
    * @see Colors#getTabHighlightColor()
    * @see Colors#setTabHighlightColor(Color)
    * @see BtnFactory#createHighlightColorBtn()
    */
   public static ActionListener createTabHighlightColorActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Show the color chooser so that the user can select a new color.
            Color color = JColorChooser.showDialog((Component)ae.getSource(),
                                                   Constants.HIGHLIGHT_COLOR_TEXT,
                                                   Colors.getTabHighlightColor());

            // Only proceed if a color was selected.
            if(null != color) {
               // Register the text pane foreground color to the newly selected color.
               Colors.setTabHighlightColor(color);

               // Loop through all existing text panes.
               for(int tabIndex = 0 ; tabIndex < Constants.TAB_PANE.getTabCount() ; ++tabIndex) {
                  // Get the text pane.
                  final DCTextEditor TEXT_PANE = Util.getTextPane(tabIndex);

                  // Set the text pane foreground color to the newly selected color.
                  TEXT_PANE.setHL(color);
               } // for each tab

               // Updated the highlight color button's background to the newly selected color.
               Constants.HIGHLIGHT_COLOR_BTN.setBackground(Colors.getTabHighlightColor());
            } // END if a color was selected
         } // END actionPerformed
      };
   } // END createTabHighlightColorActionListener

   /**
    * createFindActionListener method<br/>
    * This method defines and returns a new action listener which shows the find dialog for the current text pane.<br/>
    * When the user clicks &quot;Find&quot; from either the &quot;Edit&quot; menu or the context menu, this method is invoked.
    * @return ActionListener The object which handles the display of the find dialog
    * @see Util#getSelectedTextPane()
    * @see FindReplaceDialog#getSharedInstance(Frame)
    * @see FindReplaceDialog#showFind(DCTextEditor)
    * @see MenuFactory#createEditMenu()
    * @see MenuFactory#createTabPaneContextMenu()
    */
   public static ActionListener createFindActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Launch the find dialog
            FindReplaceDialog.getSharedInstance(null).showFind(Util.getSelectedTextPane());
         } // END actionPerformed
      };
   } // END createFindActionListener

   /**
    * createReplaceActionListener method<br/>
    * This method defines and returns a new action listener which shows the replace dialog for the current text pane.<br/>
    * When the user clicks &quot;Replace&quot; from either the &quot;Edit&quot; menu or the context menu, this method is invoked.
    * @return ActionListener The object which handles the display of the replace dialog
    * @see Util#getSelectedTextPane()
    * @see FindReplaceDialog#getSharedInstance(Frame)
    * @see FindReplaceDialog#showReplace(DCTextEditor)
    * @see MenuFactory#createEditMenu()
    * @see MenuFactory#createTabPaneContextMenu()
    */
   public static ActionListener createReplaceActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Launch the replace dialog
            FindReplaceDialog.getSharedInstance(null).showReplace(Util.getSelectedTextPane());
         } // END actionPerformed
      };
   } // END createReplaceActionListener

   /**
    * createViewEnvironmentPropertiesActionListener method<br/>
    * This method defines and returns a new action listener which creates a new text pane,
    * gets JVM environment information from the System class, and writes the information to the new text pane.<br/>
    * When the user clicks &quot;View Env&quot; from the &quot;Edit&quot; menu, this method is invoked.
    * @return ActionListener The object which handles the output of JVM environment information into a new text pane
    * @see Util#addNewTab(String)
    * @see Util#getSelectedTextPane()
    * @see DCTextEditor#setText(String)
    * @see MenuFactory#createEditMenu()
    */
   public static ActionListener createViewEnvironmentPropertiesActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Create a new text pane which will display the JVM environment information.
            Util.addNewTab(Constants.ENV_TEXT);

            try {
               // Create a map which will hold all the JVM environment information.
               TreeMap<String,String> envMap = new TreeMap<String,String>(Collator.getInstance());

               // Loop through all the system properties, adding them to the map.
               for(String key : System.getProperties().stringPropertyNames()) {
                  String value = System.getProperties().getProperty(key);
                  envMap.put(key,value);
               } // for each environment variable.

               // Loop through all the system environment properties, adding them to the map.
               for(String key : System.getenv().keySet()) {
                  String value = System.getenv().get(key);
                  envMap.put(key,value);
               } // for each environment variable.

               // Create a string representation of the map.
               String envStr = envMap.toString();
               envStr = envStr.substring(1, envStr.length() - 1);
               envStr = envStr.replace(", ",System.getProperty(Constants.LINE_SEPARATOR_PROPERTY));
               envStr = envStr.replace("=", " = ");

               // Write the string system property information to the new text pane.
               Util.getSelectedTextPane().setText(envStr);
            } catch(Exception e) {
               System.out.println(e.getMessage());
            } // END try/catch
         } // END actionPerformed
      };
   } // END createViewEnvironmentPropertiesActionListener

   /**
    * createJavaDocGenActionListener method<br/>
    * This method defines and returns a new action listener which shows the javadoc generation dialog.<br/>
    * When the user clicks &quot;Gen Java Doc&quot; from the &quot;Edit&quot; menu, this method is invoked.
    * @return ActionListener The object which handles the display of the javadoc generation dialog
    * @see DialogFactory#createJavaDocGenDialog()
    * @see MenuFactory#createEditMenu()
    */
   public static ActionListener createJavaDocGenActionListener() {
      return new ActionListener() {
         final JDialog DIALOG = DialogFactory.createJavaDocGenDialog();

         public void actionPerformed(ActionEvent ae) {
            DIALOG.setVisible(true);
         } // END actionPerformed
      };
   } // END createJavaDocGenActionListener

   /**
    * createGetUniqueNumActionListener method<br/>
    * This method defines and returns a new action listener which gets a unique number (based on current system date,)<br/>
    * creates a new text pane, and writes the unique number information into the text pane.<br/>
    * The unique number is presented in two ways: the number alone, and a java variable for serializable classes.<br/>
    * The time-stamp of when the number was generated is also written.
    * @return ActionListener The object which handles the output of a unique number (based on current system date) into a new text pane
    * @see Util#addNewTab(String)
    * @see Util#getSelectedTextPane()
    * @see DCTextEditor#setText(String)
    */
   public static ActionListener createGetUniqueNumActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Create a new text pane which will display the unique number.
            Util.addNewTab(Constants.NEW_DISTINCT_NUM_TEXT);

            try {
               // Get a unique number based on the system date.
               final long UNIQUE_NUM = System.currentTimeMillis();

               // Get the newline separator for formatting the information.
               final String NEW_LINE = System.getProperty(Constants.LINE_SEPARATOR_PROPERTY);

               // create and define a string buffer to show the unique number information.
               StringBuffer strBuffer = new StringBuffer();
               strBuffer.append("New Unique Number");
               strBuffer.append(NEW_LINE);
               strBuffer.append(String.valueOf(UNIQUE_NUM));
               strBuffer.append(NEW_LINE);
               strBuffer.append(NEW_LINE);
               strBuffer.append("/** Serial Version UID version for serializable classes */");
               strBuffer.append(NEW_LINE);
               strBuffer.append("public static final long serialVersionUID = ");
               strBuffer.append(UNIQUE_NUM);
               strBuffer.append("L;");
               strBuffer.append(NEW_LINE);
               strBuffer.append(NEW_LINE);
               strBuffer.append("Created on");
               strBuffer.append(NEW_LINE);
               strBuffer.append(new SimpleDateFormat("MM/dd/yyyy @ hh:mm:ss a").format(Calendar.getInstance().getTime()));

               // Write the unique number information into the new text pane.
               Util.getSelectedTextPane().setText(strBuffer.toString());
            } catch(Exception e) {
               System.out.println(e.getMessage());
            } // END try/catch
         } // END actionPerformed
      };
   } // END createGetUniqueNumActionListener

   /**
    * createDicePanelActionListener method<br/>
    * This method defines and returns a new action listener which shows the dice dialog.<br/>
    * When the user clicks &quot;Roll Dice&quot; from the &quot;Edit&quot; menu, this method is invoked.
    * @return ActionListener The object which handles the display of the dice dialog
    * @see DialogFactory#createDiceDialog()
    * @see MenuFactory#createEditMenu()
    */
   public static ActionListener createDicePanelActionListener() {
      return new ActionListener() {
         private final JDialog DIALOG = DialogFactory.createDiceDialog();

         public void actionPerformed(ActionEvent ae) {
            DIALOG.setVisible(true);
         } // END actionPerformed
      };
   } // END createDicePanelActionListener

   /**
    * createDiceModPanelActionListener method<br/>
    * This method defines and returns a new action listener which shows the dice mod dialog.<br/>
    * When the user clicks &quot;Roll Dice Mod&quot; from the &quot;Edit&quot; menu, this method is invoked.
    * @return ActionListener The object which handles the display of the javadoc generation dialog
    * @see DialogFactory#createDiceModDialog()
    * @see MenuFactory#createEditMenu()
    */
   public static ActionListener createDiceModPanelActionListener() {
      return new ActionListener() {
         private final JDialog DIALOG = DialogFactory.createDiceModDialog();

         public void actionPerformed(ActionEvent ae) {
            DIALOG.setVisible(true);
         } // END actionPerformed
      };
   } // END createDiceModPanelActionListener

   /**
    * createCharacterGenActionListener method<br/>
    * This method defines and returns a new action listener which shows the character generator dialog.<br/>
    * When the user clicks &quot;Character Gen&quot; from the &quot;Edit&quot; menu, this method is invoked.
    * @return ActionListener The object which handles the display of the character generator dialog
    * @see DialogFactory#createCharacterGenDialog()
    * @see MenuFactory#createEditMenu()
    */
   public static ActionListener createCharacterGenActionListener() {
      return new ActionListener() {
         private final JDialog DIALOG = DialogFactory.createCharacterGenDialog();

         public void actionPerformed(ActionEvent ae) {
            // Reset all character fields.
            Constants.SPRITE_SPINNER.setSelectedImage(0);
            Constants.CHARACTER_GENDER_LIST.setSelectedIndex(0);
            Constants.CHARACTER_RACES_LIST.setSelectedIndex(0);
            Constants.CHARACTER_CLASSES_LIST.setSelectedIndex(0);
            Constants.ZODIAC_LIST.setSelectedIndex(0);
            Constants.CHARACTER_NAME_FIELD.setText("");

            // Loop through all character stats.
            for(final DCTextField CHA_STAT_FIELD : Constants.CHARACTER_STAT_LIST) {
               CHA_STAT_FIELD.setText("");
            } // END stat loop

            // Display the character dialog.
            DIALOG.setVisible(true);
         } // END actionPerformed
      };
   } // END createCharacterGenActionListener

   /**
    * createCodecActionListener method<br/>
    * This method defines and returns a new action listener which shows the codec dialog.<br/>
    * When the user clicks &quot;Encode / Decode&quot; from the &quot;Edit&quot; menu, this method is invoked.
    * @return ActionListener The object which handles the display of the codec dialog
    * @see DialogFactory#createCodecDialog()
    * @see MenuFactory#createEditMenu()
    */
   public static ActionListener createCodecActionListener() {
      return new ActionListener() {
         private final JDialog DIALOG = DialogFactory.createCodecDialog();

         public void actionPerformed(ActionEvent ae) {
            Constants.CODEC_TEXT_AREA.setText("");
            DIALOG.setVisible(true);
         } // END actionPerformed
      };
   } // END createCodecActionListener

   /**
    * createSearchActionListener method<br/>
    * This method defines and returns a new action listener which shows the search dialog.<br/>
    * When the user clicks &quot;Search&quot; from the &quot;Edit&quot; menu, this method is invoked.
    * @return ActionListener The object which handles the display of the search dialog
    * @see DialogFactory#createSearchDialog()
    * @see MenuFactory#createEditMenu()
    */
   public static ActionListener createSearchActionListener() {
      return new ActionListener() {
         final JDialog DIALOG = DialogFactory.createSearchDialog();

         public void actionPerformed(ActionEvent ae) {
            DIALOG.setVisible(true);
         } // END actionPerformed
      };
   } // END createSearchActionListener

   /**
    * createCalculatorActionListener method<br/>
    * This method defines and returns a new action listener which shows the calculator dialog.<br/>
    * When the user clicks &quot;Calculator&quot; from the &quot;Edit&quot; menu, this method is invoked.
    * @return ActionListener The object which handles the display of the calculator dialog
    * @see Calculator
    * @see MenuFactory#createEditMenu()
    */
   public static ActionListener createCalculatorActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            final Calculator CALC = new Calculator();
            CALC.setVisible(true);
         } // END actionPerformed
      };
   } // END createCalculatorActionListener

   /**
    * createRollDiceActionListener method<br/>
    * This method defines and returns a new action listener which rolls dice and displays the dice stats in a new tab pane.<br/>
    * When the user clicks the &quot;Roll&quot; button in the &quot;Dice Dialog&quot;, this method is invoked.
    * @return ActionListener The object which handles the dice rolling and display of dice stats in a new tab pane
    * @see BtnFactory#createRollDiceBtn()
    */
   public static ActionListener createRollDiceActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Create a new text pane to contain the dice roll information.
            Util.addNewTab(Constants.ROLL_DICE_RESULTS_TEXT);

            try {
               // Create number formatters to represent the dice roll numbers.
               DecimalFormat rollNumFormat = new DecimalFormat("000");
               DecimalFormat rollAvgFormat = new DecimalFormat("0.000");

               // Create a string buffer to contain the dice roll information.
               StringBuffer strBuffer = new StringBuffer();

               // For each die in the dice array.
               for(final IntegerSpinner QTY_SPINNER : Constants.DICE_SPINNERS) {
                  // Get the number of dice to roll.  This is the value of the spinner.
                  final int DIE_QTY = QTY_SPINNER.getNumber();

                  // Get the die number (this should be the number of faces,) replace all non digits in label.
                  final int DIE_NUM = Integer.parseInt(QTY_SPINNER.getLabel().getText().replaceAll("\\D", ""));

                  // Get the newline separator for formatting the information.
                  final String NEW_LINE = System.getProperty(Constants.LINE_SEPARATOR_PROPERTY);

                  // Only create data if this die should be rolled.
                  if(DIE_QTY > 0) {
                     // Keep track of aggregate data.
                     int dieTotal = 0;
                     ArrayList<Integer> dieRolls = new ArrayList<Integer>(DIE_QTY);

                     // Print die header info.
                     strBuffer.append(QTY_SPINNER.getLabel().getText());
                     strBuffer.append(NEW_LINE);
                     strBuffer.append(QTY_SPINNER.getLabel().getText().replaceAll(".", "="));
                     strBuffer.append(NEW_LINE);

                     // For each dice roll.
                     for(int rollNum = 1; rollNum <= DIE_QTY; ++rollNum) {
                        // Roll the die.
                        int dieRoll = Util.getRandomInt(DIE_NUM);
                        dieTotal += dieRoll;
                        dieRolls.add(dieRoll);

                        // Print die roll data.
                        strBuffer.append(rollNumFormat.format(rollNum));
                        strBuffer.append(": ");
                        strBuffer.append(dieRoll);
                        strBuffer.append(NEW_LINE);
                     } // END loop

                     // Check if dice stats were requested
                     if(Constants.GEN_DICE_STATS_CHECK_BOX.isSelected()) {
                        // Sort the die rolls.
                        Integer[] dieRollsSorted = dieRolls.toArray(new Integer[dieRolls.size()]);
                        Arrays.sort(dieRollsSorted);

                        // Print die aggregate data.
                        strBuffer.append(QTY_SPINNER.getLabel().getText().replaceAll(".", "="));
                        strBuffer.append(NEW_LINE);
                        strBuffer.append("Total:   ");
                        strBuffer.append(dieTotal);
                        strBuffer.append(NEW_LINE);
                        strBuffer.append("Average: ");
                        strBuffer.append(rollAvgFormat.format((double)dieTotal / (double)DIE_QTY));
                        strBuffer.append(NEW_LINE);
                        strBuffer.append("Minimum: ");
                        strBuffer.append(dieRollsSorted[0]);
                        strBuffer.append(NEW_LINE);
                        strBuffer.append("Maximum: ");
                        strBuffer.append(dieRollsSorted[dieRollsSorted.length - 1]);
                        strBuffer.append(NEW_LINE);
                        strBuffer.append("Range:   ");
                        strBuffer.append(dieRollsSorted[dieRollsSorted.length - 1] - dieRollsSorted[0]);
                        strBuffer.append(NEW_LINE);
                        strBuffer.append(NEW_LINE);
                     } // END if
                  } // END if
               } // END loop

               // Write the dice roll information to the new text pane.
               Util.getSelectedTextPane().setText(strBuffer.toString());
            } catch(Exception e) {
               System.out.println(e.getMessage());
            } // END try/catch
         } // END actionPerformed
      };
   } // END createRollDiceActionListener

   /**
    * createResetDiceActionListener method<br/>
    * This method defines and returns a new action listener which resets the dice roll spinners in the dice dialog.<br/>
    * When the user clicks the &quot;Reset&quot; button in the &quot;Dice Dialog&quot;, this method is invoked.
    * @return ActionListener The object which handles the resetting of the dice roll spinners in the dice dialog.
    * @see BtnFactory#createResetDiceBtn()
    * @see IntegerSpinner#resetNumber()
    */
   public static ActionListener createResetDiceActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // For each dice spinner
            for(final IntegerSpinner QTY_SPINNER : Constants.DICE_SPINNERS) {
               QTY_SPINNER.resetNumber();
            } // END loop

            // Deselect the dice stats check box.
            Constants.GEN_DICE_STATS_CHECK_BOX.setSelected(false);
         } // END actionPerformed
      };
   } // END createResetDiceActionListener

   /**
    * createRollDiceModActionListener method<br/>
    * This method defines and returns a new action listener which rolls dice, modifies the roll, and displays the dice stats in a new tab pane.<br/>
    * When the user clicks the &quot;Roll&quot; button in the &quot;Dice Mod Dialog&quot;, this method is invoked.
    * @return ActionListener The object which handles the dice rolling and display of modified dice stats in a new tab pane
    * @see BtnFactory#createRollDiceModBtn()
    */
   public static ActionListener createRollDiceModActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Create a new text pane to contain the dice roll information.
            Util.addNewTab(Constants.ROLL_DICE_RESULTS_TEXT);

            try {
               // Create a number formatter to represent the dice roll number.
               DecimalFormat rollNumFormat = new DecimalFormat("000");

               // Create a string buffer to contain the dice roll information.
               StringBuffer strBuffer = new StringBuffer();

               // Get the newline separator for formatting the information.
               final String NEW_LINE = System.getProperty(Constants.LINE_SEPARATOR_PROPERTY);

               // Keep track of aggregate data.
               final int DIE_QTY = Constants.DICE_QTY_SPINNER.getNumber();
               final int DIE_NUM = Constants.DICE_NUM_SIDES_SPINNER.getNumber();
               final int DIE_MOD = Constants.DICE_MOD_SPINNER.getNumber();
               final int MOD_INDEX = Constants.DICE_MOD_OPTIONS_LIST.getSelectedIndex();
               int dieTotal = 0;
               ArrayList<Integer> dieRolls = new ArrayList<Integer>(DIE_QTY);

               // Print die header info.
               strBuffer.append(DIE_QTY + "D" + DIE_NUM);
               strBuffer.append(NEW_LINE);
               strBuffer.append(Integer.toString(DIE_QTY).replaceAll(".", "=") + "=" + Integer.toString(DIE_NUM).replaceAll(".", "="));
               strBuffer.append(NEW_LINE);

               // For each dice roll.
               for(int rollNum = 1 ; rollNum <= DIE_QTY ; ++rollNum) {
                  // Roll the die.
                  int dieRoll = Util.getRandomInt(DIE_NUM);
                  dieTotal += dieRoll;
                  dieRolls.add(dieRoll);

                  // Print die roll data.
                  strBuffer.append(rollNumFormat.format(rollNum));
                  strBuffer.append(": ");
                  strBuffer.append(dieRoll);
                  strBuffer.append(NEW_LINE);
               } // END loop

               // DICE_MOD_OPTIONS = new String[] {"None", "Add", "Subtract", "Multiply", "Divide", "Highest", "Lowest"};
               if(MOD_INDEX == 0) { // None
                  strBuffer.append("Total: ");
               } else {
                  strBuffer.append("Sub-Total: ");
               } // END if/else

               strBuffer.append(dieTotal);

               // DICE_MOD_OPTIONS = new String[] {"None", "Add", "Subtract", "Multiply", "Divide", "Highest", "Lowest"};
               if(MOD_INDEX == 1) { // Add
                  strBuffer.append(NEW_LINE);
                  strBuffer.append(dieTotal);
                  strBuffer.append(" + ");
                  strBuffer.append(DIE_MOD);
                  dieTotal += DIE_MOD;
                  strBuffer.append(" = ");
                  strBuffer.append(dieTotal);
                  strBuffer.append(NEW_LINE);
                  strBuffer.append("Total: ");
                  strBuffer.append(dieTotal);
               } else if(MOD_INDEX == 2) { // Subtract
                  strBuffer.append(NEW_LINE);
                  strBuffer.append(dieTotal);
                  strBuffer.append(" - ");
                  strBuffer.append(DIE_MOD);
                  dieTotal -= DIE_MOD;
                  strBuffer.append(" = ");
                  strBuffer.append(dieTotal);
                  strBuffer.append(NEW_LINE);
                  strBuffer.append("Total: ");
                  strBuffer.append(dieTotal);
               } else if(MOD_INDEX == 3) { // Multiply
                  strBuffer.append(NEW_LINE);
                  strBuffer.append(dieTotal);
                  strBuffer.append(" * ");
                  strBuffer.append(DIE_MOD);
                  dieTotal *= DIE_MOD;
                  strBuffer.append(" = ");
                  strBuffer.append(dieTotal);
                  strBuffer.append(NEW_LINE);
                  strBuffer.append("Total: ");
                  strBuffer.append(dieTotal);
               } else if(MOD_INDEX == 4) { // Divide
                  strBuffer.append(NEW_LINE);
                  strBuffer.append(dieTotal);
                  strBuffer.append(" / ");
                  strBuffer.append(DIE_MOD);
                  DecimalFormat rollAvgFormat = new DecimalFormat("0.000");
                  strBuffer.append(" = ");
                  strBuffer.append(rollAvgFormat.format((double)dieTotal / (double)DIE_MOD));
                  strBuffer.append(NEW_LINE);
                  strBuffer.append("Total: ");
                  strBuffer.append(dieTotal);
               } else if(MOD_INDEX == 5) { // Highest
                  // Sort the die rolls.
                  Integer[] dieRollsSorted = dieRolls.toArray(new Integer[dieRolls.size()]);
                  Arrays.sort(dieRollsSorted);

                  // Subtract lower order rolls
                  for(int dieRollIndex = (DIE_QTY - DIE_MOD - 1) ; dieRollIndex >= 0 ; --dieRollIndex) {
                     dieTotal -= dieRollsSorted[dieRollIndex];
                  } // END loop

                  strBuffer.append(NEW_LINE);
                  strBuffer.append("Highest ");
                  strBuffer.append(DIE_MOD);
                  strBuffer.append(" = ");
                  strBuffer.append(dieTotal);
                  strBuffer.append(NEW_LINE);
                  strBuffer.append("Total: ");
                  strBuffer.append(dieTotal);
               } else if(MOD_INDEX == 6) { // Lowest
                  // Sort the die rolls.
                  Integer[] dieRollsSorted = dieRolls.toArray(new Integer[dieRolls.size()]);
                  Arrays.sort(dieRollsSorted);

                  // Subtract higher order rolls
                  for(int dieRollIndex = DIE_MOD ; dieRollIndex < DIE_QTY ; ++dieRollIndex) {
                     dieTotal -= dieRollsSorted[dieRollIndex];
                  } // END loop

                  strBuffer.append(NEW_LINE);
                  strBuffer.append("Lowest ");
                  strBuffer.append(DIE_MOD);
                  strBuffer.append(" = ");
                  strBuffer.append(dieTotal);
                  strBuffer.append(NEW_LINE);
                  strBuffer.append("Total: ");
                  strBuffer.append(dieTotal);
               } // END if/else

               // Write the dice roll information to the new text pane.
               Util.getSelectedTextPane().setText(strBuffer.toString());
            } catch(Exception e) {
               System.out.println(e.getMessage());
            } // END try/catch
         } // END actionPerformed
      };
   } // END createRollDiceModActionListener

   /**
    * createResetDiceModActionListener method<br/>
    * This method defines and returns a new action listener which resets the dice mod roll spinners in the dice mod dialog.<br/>
    * When the user clicks the &quot;Reset&quot; button in the &quot;Dice Mod Dialog&quot;, this method is invoked.
    * @return ActionListener The object which handles the resetting of the dice roll spinners in the dice mod dialog
    * @see BtnFactory#createResetDiceModBtn()
    * @see IntegerSpinner#resetNumber()
    */
   public static ActionListener createResetDiceModActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Reset the dice mod spinners and option list.
            Constants.DICE_QTY_SPINNER.resetNumber();
            Constants.DICE_NUM_SIDES_SPINNER.resetNumber();
            Constants.DICE_MOD_SPINNER.resetNumber();
            Constants.DICE_MOD_OPTIONS_LIST.setSelectedIndex(0);
         } // END actionPerformed
      };
   } // END createResetDiceModActionListener

   /**
    * createTranslateDialogActionListener method<br/>
    * This method defines and returns a new action listener which translates template text into user-supplied text.<br/>
    * When the user clicks the &quot;Translate&quot; button in the &quot;Translate Template Dialog&quot;, this method is invoked.
    * @param DIALOG The template translation dialog.  This dialog is closed at the end of the translation process
    * @param TEMPLATE_TEXT The text to translate
    * @param NEW_TAB A boolean which specifies whether or not to write the translated text into a new tab pane or not
    * @return ActionListener The object which handles the translation of template text into user-supplied text
    * @see BtnFactory#createTranslateDialogBtn(JDialog, String, boolean)
    */
   public static ActionListener createTranslateDialogActionListener(final JDialog DIALOG,final String TEMPLATE_TEXT,final boolean NEW_TAB) {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            try {
               // Stop any cell edits, so that all table row values get processed.
               Constants.TEMPLATE_TOKEN_TABLE.getCellEditor().stopCellEditing();
            } catch(Exception e) {
               // Sometimes stopCellEditing() throws null.
               // This is a non-fatal error.
            } // END try/catch

            try {
               // Create a new string for the translated text.
               String translatedText = new String(TEMPLATE_TEXT);

               // Replace text tokens with whatever text the user specified.
               for(final TemplateToken TEMPLATE_TOKEN : Constants.TEMPLATE_TOKEN_TABLE_MODEL.getTemplateTokens()) {
                  if(TEMPLATE_TOKEN.getDisplayComponent() instanceof TemplateToken.TemplateTokenComboBox) {
                     final TemplateToken.TemplateTokenComboBox COMBO_BOX = (TemplateToken.TemplateTokenComboBox)TEMPLATE_TOKEN.getDisplayComponent();
                     final String TEXT = (String)COMBO_BOX.getSelectedItem();
                     translatedText = translatedText.replace(TEMPLATE_TOKEN.getTokenWithDelimiters(), TEXT);
                     continue;
                  } // END if

                  if(TEMPLATE_TOKEN.getDisplayComponent() instanceof TemplateToken.TemplateTokenTextField) {
                     final TemplateToken.TemplateTokenTextField TEXT_FIELD = (TemplateToken.TemplateTokenTextField)TEMPLATE_TOKEN.getDisplayComponent();
                     final String TEXT = TEXT_FIELD.getText();
                     translatedText = translatedText.replace(TEMPLATE_TOKEN.getTokenWithDelimiters(), TEXT);
                     continue;
                  } // END if

                  translatedText = translatedText.replace(TEMPLATE_TOKEN.getTokenWithDelimiters(), "?????");
               } // END loop

               if(NEW_TAB) {
                  // Create a new tab pane.
                  Util.addNewTab(Constants.NEW_TEXT);

                  // Select the new pane and highlight the text.
                  Util.getSelectedTextPane().setText(translatedText);
               } else {
                  // Get the currently selected text pane's document.
                  final Document DOC = Util.getSelectedTextPane().getDocument();

                  // Insert the text into the current text pane.
                  DOC.insertString(Util.getSelectedTextPane().getCaretPosition(), translatedText, null);

                  // Update tab title text
                  final String TAB_TEXT = Util.getSelectedTabText();
                  if(!TAB_TEXT.endsWith(Constants.ASTERISK_CHAR)) {
                     Util.setSelectedTabText(TAB_TEXT + Constants.ASTERISK_CHAR);
                  } // END if no asterisk
               } // END if/else
            } catch(Exception e) {
               System.out.println(e.getMessage());
            } // END try/catch

            // Hide the translation dialog
            DIALOG.setVisible(false);
         } // END actionPerformed
      };
   } // END createTranslateDialogActionListener

   /**
    * createReloadTemplateTokensActionListener method<br/>
    * This method defines and returns a new key listener which populates table data within the translation dialog.<br/>
    * When the user presses a key in either of the template text fields, this method is invoked.
    * @return KeyAdapter The object which handles the table data within the translation dialog
    * @see Constants#TEMPLATE_TOKEN_TABLE_MODEL
    * @see Util#updateTemplateTokenTableModel()
    * @see DialogFactory#createTranslateTemplateDialog(String, boolean)
    */
   public static KeyAdapter createReloadTemplateTokensActionListener() {
      return new KeyAdapter() {
         /** keyReleased method */
         public void keyReleased(KeyEvent ke) {
            // Clear current model data.
            Constants.TEMPLATE_TOKEN_TABLE_MODEL.clear();

            // Update current model data.
            Util.updateTemplateTokenTableModel();

            // Fire an update to the model, this will refresh the table.
            Constants.TEMPLATE_TOKEN_TABLE_MODEL.fireTableDataChanged();
         } // END keyReleased method
      };
   } // END createReloadTemplateTokensActionListener

   /**
    * createLookAndFeelActionListener method<br/>
    * This method defines and returns a new action listener which sets the look and feel,
    * and repaints the entire application based on the selected look and feel.<br/>
    * When the user selects an entry from the &quot;L&ampF&quot; menu, this method is invoked.
    * @param APP The application object which calls repaint() in order to update its children
    * @param LF_NAME The look and feel name to set so that the app can repaint itself
    * @return ActionListener The object which handles setting the look and feel of the application
    * @see MenuFactory#createLookAndFeelMenu(DCEdit)
    */
   public static ActionListener createLookAndFeelActionListener(final DCEdit APP,final String LF_NAME) {
      return new ActionListener() {
         // This listener is used to set the tab pane text back to the user-selected font.
         final ActionListener AL = createTabPaneFontActionListener();

         /** actionPerformed method */
         public void actionPerformed(final ActionEvent AE) {
            // Execute the following logic off of the event-dispatch thread, which prevents lock-up.
            SwingUtilities.invokeLater(new Runnable() {
               public void run() {
                  try {
                     // Create the fully qualified look and feel name string.
                     final String LF_CLASS = Constants.LF_PREFIX + LF_NAME + Constants.LF_SUFFIX;

                     // Create the fully qualified skin name string.
                     final String SKIN_CLASS = Constants.SKIN_PREFIX + LF_NAME + Constants.SKIN_SUFFIX;

                     // Activate the new look and feel for the entire app.
                     UIManager.setLookAndFeel(LF_CLASS);
                     SubstanceLookAndFeel.setSkin(SKIN_CLASS);

                     // Get the label color from the new look and feel.
                     final Color LABEL_COLOR = (Color)UIManager.get("Label.foreground");

                     // Update spinner editor styles.
                     for(final IntegerSpinner SPINNER : Constants.DICE_SPINNERS) {
                        SPINNER.setForeground(LABEL_COLOR);
                     } // END loop

                     // Update character stat field styles.
                     for(final DCTextField CHA_STAT_FIELD : Constants.CHARACTER_STAT_LIST) {
                        CHA_STAT_FIELD.setForeground(LABEL_COLOR);
                        CHA_STAT_FIELD.setDisabledTextColor(LABEL_COLOR);
                        CHA_STAT_FIELD.setBorder(BorderFactory.createLineBorder(LABEL_COLOR));
                     } // END loop

                     // Updated spinner and text field styles.
                     Constants.DICE_QTY_SPINNER.setForeground(LABEL_COLOR);
                     Constants.DICE_NUM_SIDES_SPINNER.setForeground(LABEL_COLOR);
                     Constants.DICE_MOD_SPINNER.setForeground(LABEL_COLOR);
                     Constants.BEGIN_TEMPLATE_EXPRESSION_FIELD.setForeground(LABEL_COLOR);
                     Constants.END_TEMPLATE_EXPRESSION_FIELD.setForeground(LABEL_COLOR);
                     Constants.JAVADOC_SOURCE_FIELD.setForeground(LABEL_COLOR);
                     Constants.JAVADOC_DESTINATION_FIELD.setForeground(LABEL_COLOR);
                     Constants.JAVADOC_SOURCE_FIELD.setForeground(LABEL_COLOR);
                     Constants.JAVADOC_STYLE_TEXT_PANE.setForeground(LABEL_COLOR);
                     Constants.CODEC_LABEL.setForeground(LABEL_COLOR);
                     Constants.CODEC_TEXT_AREA.setForeground(LABEL_COLOR);
                     Constants.CHARACTER_NAME_FIELD.setForeground(LABEL_COLOR);
                     Constants.SEARCH_PATH_FIELD.setForeground(LABEL_COLOR);
                     Constants.SEARCH_TERM_FIELD.setForeground(LABEL_COLOR);
                     Constants.SEARCH_FILE_LIST_FIELD.setForeground(LABEL_COLOR);

                     // Tell the app to repaint itself so that the new look and feel will become visible.
                     APP.repaint();

                     // The above call to repaint resets the tab pane text size and font, so we need to set the tab panes back.
                     AL.actionPerformed(AE);
                  } catch(Exception e) {
                     System.out.println("Substance Look and Feel failed to initialize.");
                  } // END try/catch
               } // END run
            });
         } // END actionPerformed
      };
   } // END createLookAndFeelActionListener

   /**
    * createTextGenFromTemplateActionListener method<br/>
    * This method defines and returns a new action listener which translates the supplied text into code text.<br/>
    * The difference between the supplied text and the translated text will be that any templates within the supplied text will be replaced by user-defined text.
    * @param TEMPLATE_PATH The text (with or without templates) to translate into code text.
    * @param OPEN_IN_NEW_TAB Whether or not to write the new text into the current tab pane or a new tab pane
    * @return ActionListener The object which handles the translation of canned text strings into code text
    * @see DialogFactory#createTranslateTemplateDialog(String, boolean)
    * @see MenuFactory#createTextGenFromTemplateMenuItem(String, String, boolean, KeyStroke)
    */
   public static ActionListener createTextGenFromInternalTemplateActionListener(final String TEMPLATE_PATH, final boolean OPEN_IN_NEW_TAB) {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            final String TEMPLATE_TEXT = Util.getTextFromFile(TEMPLATE_PATH, true);

            // Prompt to replace templates
            if(TEMPLATE_TEXT.contains(Constants.BEGIN_TEMPLATE_EXPRESSION_FIELD.getText()) &&
               TEMPLATE_TEXT.contains(Constants.END_TEMPLATE_EXPRESSION_FIELD.getText())) {
               DialogFactory.createTranslateTemplateDialog(TEMPLATE_TEXT,OPEN_IN_NEW_TAB).setVisible(true);
            } else {
               final Document DOC = Util.getSelectedTextPane().getDocument();

               try {
                  if(OPEN_IN_NEW_TAB) {
                     // Create a new tab pane.
                     Util.addNewTab(Constants.NEW_TEXT);

                     // Select the new pane and highlight the text.
                     Util.getSelectedTextPane().setText(TEMPLATE_TEXT);
                  } else {
                     // Insert the text into the text pane.
                     DOC.insertString(Util.getSelectedTextPane().getCaretPosition(), TEMPLATE_TEXT, null);
                  } // END if/else
               } catch(Exception e) {
                  System.out.println(e.getMessage());
               } // END try/catch

               // Update tab title text
               final String TAB_TEXT = Util.getSelectedTabText();
               if(!TAB_TEXT.endsWith(Constants.ASTERISK_CHAR)) {
                  Util.setSelectedTabText(TAB_TEXT + Constants.ASTERISK_CHAR);
               } // END if no asterisk
            } // END if/else
         } // END actionPerformed
      };
   } // END createTextGenFromInternalTemplateActionListener

   /**
    * createTextGenFromTemplateActionListener method<br/>
    * This method defines and returns a new action listener which translates the supplied text into code text.<br/>
    * The difference between the supplied text and the translated text will be that any templates within the supplied text will be replaced by user-defined text.
    * @param MENU The menu reference.
    * @param MENU_ITEM The menu item reference.
    * @param TEMPLATE_NAME The simple name of the template.  The template name without path and file extension.
    * @param TEMPLATE_PATH The text (with or without templates) to translate into code text.
    * @return ActionListener The object which handles the translation of canned text strings into code text
    * @see DialogFactory#createTranslateTemplateDialog(String, boolean)
    */
   public static ActionListener createTextGenFromExternalTemplateActionListener(final JMenu MENU, final JMenuItem MENU_ITEM, final String TEMPLATE_NAME, final String TEMPLATE_PATH) {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            if(InputEvent.BUTTON3_MASK == ae.getModifiers()) {
               final int CHOICE = JOptionPane.showConfirmDialog(null, String.format("Would you like to delete this template: '%s'?", TEMPLATE_NAME));

               if(JOptionPane.YES_OPTION == CHOICE) {
                  final boolean RESULT = FileUtils.deleteQuietly(new File(TEMPLATE_PATH));
                  if(RESULT) MENU.remove(MENU_ITEM);
                  if(RESULT) JOptionPane.showMessageDialog(null, String.format("Template: '%s' was successfully deleted.", TEMPLATE_NAME));
                  else JOptionPane.showMessageDialog(null, String.format("Unable to delete template: '%s'.  The file might be locked by another program.", TEMPLATE_NAME));
                  return;
               } else if(JOptionPane.CANCEL_OPTION == CHOICE) {
                  return;
               } // END if/else
            } // END if

            final String TEMPLATE_TEXT = Util.getTextFromFile(TEMPLATE_PATH, false);

            // Prompt to replace templates
            if(TEMPLATE_TEXT.contains(Constants.BEGIN_TEMPLATE_EXPRESSION_FIELD.getText()) &&
               TEMPLATE_TEXT.contains(Constants.END_TEMPLATE_EXPRESSION_FIELD.getText())) {
               DialogFactory.createTranslateTemplateDialog(TEMPLATE_TEXT,true).setVisible(true);
            } else {
               try {
                  // Create a new tab pane.
                  Util.addNewTab(Constants.NEW_TEXT);

                  // Select the new pane and highlight the text.
                  Util.getSelectedTextPane().setText(TEMPLATE_TEXT);
               } catch(Exception e) {
                  System.out.println(e.getMessage());
               } // END try/catch

               // Update tab title text
               final String TAB_TEXT = Util.getSelectedTabText();
               if(!TAB_TEXT.endsWith(Constants.ASTERISK_CHAR)) {
                  Util.setSelectedTabText(TAB_TEXT + Constants.ASTERISK_CHAR);
               } // END if no asterisk
            } // END if/else
         } // END actionPerformed
      };
   } // END createTextGenFromExternalTemplateActionListener

   /**
    * createCustomMenuItemDeleteItemListener method<br/>
    * This method defines and returns a new mouse adapter which prompts the user if (s)he wants to delete the custom menu item.
    * @param TEMPLATE_PATH The text (with or without templates) to translate into code text.
    * @return MouseAdapter The object which handles the mouse click event.
    */
   public static MouseAdapter createCustomMenuItemDeleteItemListener(final String TEMPLATE_PATH) {
      return new MouseAdapter() {
         public void mouseClicked(MouseEvent me) {
            if(SwingUtilities.isRightMouseButton(me)) {
               // TODO
               System.out.println("Hello: " + TEMPLATE_PATH);
            } // END if
         } // END mouseClicked
      };
   } // END createCustomMenuItemDeleteItemListener

   /**
    * createGenStatsDialogActionListener method<br/>
    * This method defines and returns a new action listener which generates a character's stats.<br/>
    * This method is used by the &quot;Character Generation&quot; dialog.<br/>
    * When the user clicks the &quot;Gen.Stats&quot; button, this method is invoked.
    * @return ActionListener The object which handles the generation of character stats for the Character Generation dialog
    * @see Util#getRandomInt(int)
    * @see BtnFactory#createGenStatsDialogBtn()
    */
   public static ActionListener createGenStatsDialogActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Create dice number and quantity constants.
            final int DIE_NUM = 12;
            final int DIE_QTY = 5;

            // For each character stat field.
            for(final DCTextField CHA_STAT_FIELD : Constants.CHARACTER_STAT_LIST) {
               int total = 0;

               // For each dice roll.
               for(int rollNum = 1; rollNum <= DIE_QTY; ++rollNum) {
                  try {
                     total += Util.getRandomInt(DIE_NUM);
                  } catch(Exception e) {
                     System.out.println(e.getMessage());
                  } // END try/catch
               } // END dice roll loop.

               CHA_STAT_FIELD.setText(String.valueOf(total));
               CHA_STAT_FIELD.setHorizontalAlignment(JTextField.CENTER);
            } // END stat loop
         } // END actionPerformed
      };
   } // END createGenStatsDialogActionListener

   /**
    * createCharacterGenSaveActionListener method<br/>
    * This method defines and returns a new action listener which saves a DCCharacter object to disk.<br/>
    * When the user selects &quot;Save&quot; from the &quot;File&quot; menu within the &quot;Character Generation&quot; dialog, this method is invoked.
    * @return ActionListener The object which handles character save operations
    * @see DCCharacter#DCCharacter(OmniImage, String, String, String, String, String, int, int, int, int, int, int, int)
    * @see Util#writeDCCharacter(DCCharacter)
    * @see MenuFactory#createCharacterGenFileMenu(JDialog)
    */
   public static ActionListener createCharacterGenSaveActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Create string buffer for validation notes.
            StringBuilder validationNotes = new StringBuilder(1024);

            // Validate character fields.
            if(Constants.CHARACTER_NAME_FIELD.getText().length() < 3) {
               validationNotes.append("Name ");
               validationNotes.append(Constants.CHARACTER_NAME_FIELD.getText());
               validationNotes.append(" must be at least 3 characters long.");
            } // END if

            // For each character stat field.
            for(final DCTextField CHA_STAT_FIELD : Constants.CHARACTER_STAT_LIST) {
               if(CHA_STAT_FIELD.isEmpty()) {
                  if(!validationNotes.toString().isEmpty()) {
                     validationNotes.append(Constants.NEW_LINE);
                     validationNotes.append(Constants.NEW_LINE);
                  } // END if
                  validationNotes.append(CHA_STAT_FIELD.getLabel().getText());
                  validationNotes.append(" field must contain a numeric value.");
               } // END if
            } // END stat loop

            // Only create character object if there were no validation notes
            if(validationNotes.toString().isEmpty()) {
               // Create new DC Character object
               final DCCharacter CHARACTER = new DCCharacter(
                  Constants.SPRITE_SPINNER.getCurrentlySelectedImage(),
                  (String)Constants.CHARACTER_GENDER_LIST.getSelectedItem(),
                  (String)Constants.CHARACTER_RACES_LIST.getSelectedItem(),
                  (String)Constants.CHARACTER_CLASSES_LIST.getSelectedItem(),
                  (String)Constants.ZODIAC_LIST.getSelectedItem(),
                  Constants.CHARACTER_NAME_FIELD.getText(),
                  Integer.parseInt(Constants.CHARACTER_STAT_LIST[0].getText()),
                  Integer.parseInt(Constants.CHARACTER_STAT_LIST[1].getText()),
                  Integer.parseInt(Constants.CHARACTER_STAT_LIST[2].getText()),
                  Integer.parseInt(Constants.CHARACTER_STAT_LIST[3].getText()),
                  Integer.parseInt(Constants.CHARACTER_STAT_LIST[4].getText()),
                  Integer.parseInt(Constants.CHARACTER_STAT_LIST[5].getText()),
                  Integer.parseInt(Constants.CHARACTER_STAT_LIST[6].getText())
               );

               // Save the character to disc
               Util.writeDCCharacter(CHARACTER);
            } else {
               JOptionPane.showMessageDialog(null, validationNotes.toString(), "Character Validation", JOptionPane.ERROR_MESSAGE);
            } // END if/else
         } // END actionPerformed
      };
   } // END createCharacterGenSaveActionListener

   /**
    * createCharacterGenLoadActionListener method<br/>
    * This method defines and returns a new action listener which loads a DCCharacter object from disk.<br/>
    * When the user selects &quot;Load&quot; from the &quot;File&quot; menu within the &quot;Character Generation&quot; dialog, this method is invoked.
    * @return ActionListener The object which handles the loading of a DCCharacter from disk
    * @see Util#getListOfDCCharacters()
    * @see DialogFactory#createCharacterGenLoadDialog()
    * @see MenuFactory#createCharacterGenFileMenu(JDialog)
    */
   public static ActionListener createCharacterGenLoadActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            if(Util.getListOfDCCharacters().isEmpty()) {
               JOptionPane.showMessageDialog(null, "There are no saved characters which to load.", "Character Load", JOptionPane.INFORMATION_MESSAGE);
            } else {
               DialogFactory.createCharacterGenLoadDialog().setVisible(true);
            } // END if/else
         } // END actionPerformed
      };
   } // END createCharacterGenLoadActionListener

   /**
    * createCharacterGenLoadSelectionListener method<br/>
    * This method defines and returns a list selection listener which loads a selected character from disk, into the Character Generation dialog.<br/>
    * When the user clicks on a name in the &quot;Character Selection&quot; dialog, this method is invoked.
    * @param DIALOG The Character Selection dialog.  This dialog just gets closed after a character is loaded into the Character Generation dialog.
    * @param DC_CHARACTER_SELECTION_LIST The list of characters found on disk.
    * @return ListSelectionListener The object which handles the selection of characters which to load into the Character Generation dialog
    * @see Util#readDCCharacter(String)
    * @see DialogFactory#createCharacterGenLoadDialog()
    */
   public static ListSelectionListener createCharacterGenLoadSelectionListener(final JDialog DIALOG, final JList<String> DC_CHARACTER_SELECTION_LIST) {
      return new ListSelectionListener() {
         public void valueChanged(ListSelectionEvent lse) {
            // Get the character name.  The file name should be the same.
            final String DC_CHARACTER_NAME = (String)DC_CHARACTER_SELECTION_LIST.getSelectedValue();

            // Create a string buffer to show a confirmation message.
            StringBuffer messageNotes = new StringBuffer();
            messageNotes.append("Do you want to load ");
            messageNotes.append(DC_CHARACTER_NAME);
            messageNotes.append("?");

            // Show the confirmation message.
            final int DECISION = JOptionPane.showConfirmDialog(DC_CHARACTER_SELECTION_LIST, messageNotes.toString(), "Confirm", JOptionPane.YES_NO_OPTION);

            // Proceed if the user clicked yes.
            if(DECISION == JOptionPane.YES_OPTION) {
               Util.readDCCharacter(DC_CHARACTER_NAME);
            } // END if

            // Close the load dialog
            DIALOG.setVisible(false);
         } // END valueChanged
      };
   } // END createCharacterGenLoadSelectionListener

   /**
    * createCodecEncodeDecodeActionListener method<br/>
    * This method defines and returns a new action listener which compresses/decompresses clear-text to/from cypher-text.<br/>
    * When the user selects &quot;Encode / Decode&quot; from the &quot;Encode / Decode&quot; dialog, this method is invoked.
    * @return ActionListener The object which handles the compression/decompression of clear-text to/from cypher-text
    * @see Base64
    * @see Base32
    * @see DigestUtils
    * @see BinaryCodec
    * @see BtnFactory#createCodecEncodeDecodeBtn()
    */
   public static ActionListener createCodecEncodeDecodeActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Get the dialog text and the selected option.
            final String INITIAL_TEXT = Constants.CODEC_TEXT_AREA.getText();
            final String SELECTED_ITEM = (String)Constants.CODEC_COMBO_BOX.getSelectedItem();

            // Compress/decompress the text based on the user's selected option.
            if(SELECTED_ITEM.equalsIgnoreCase("PlainText => Base64")) {
               final String FINAL_TEXT = new String(Base64.encodeBase64(INITIAL_TEXT.getBytes()));
               Constants.CODEC_TEXT_AREA.setText(FINAL_TEXT);
            } else if(SELECTED_ITEM.equalsIgnoreCase("Base64 => PlainText")) {
               final String FINAL_TEXT = new String(Base64.decodeBase64(INITIAL_TEXT.getBytes()));
               Constants.CODEC_TEXT_AREA.setText(FINAL_TEXT);
            } else if(SELECTED_ITEM.equalsIgnoreCase("PlainText => Base32")) {
               final Base32 CODEC = new Base32();
               final String FINAL_TEXT = new String(CODEC.encode(INITIAL_TEXT.getBytes()));
               Constants.CODEC_TEXT_AREA.setText(FINAL_TEXT);
            } else if(SELECTED_ITEM.equalsIgnoreCase("Base32 => PlainText")) {
               final Base32 CODEC = new Base32();
               final String FINAL_TEXT = new String(CODEC.decode(INITIAL_TEXT.getBytes()));
               Constants.CODEC_TEXT_AREA.setText(FINAL_TEXT);
            } else if(SELECTED_ITEM.equalsIgnoreCase("PlainText => MD5")) {
               final String FINAL_TEXT = new String(DigestUtils.md5Hex(INITIAL_TEXT.getBytes()));
               Constants.CODEC_TEXT_AREA.setText(FINAL_TEXT);
            } else if(SELECTED_ITEM.equalsIgnoreCase("PlainText => SHA")) {
               final String FINAL_TEXT = new String(DigestUtils.shaHex(INITIAL_TEXT.getBytes()));
               Constants.CODEC_TEXT_AREA.setText(FINAL_TEXT);
            } else if(SELECTED_ITEM.equalsIgnoreCase("PlainText => Binary")) {
               final String FINAL_TEXT = new String(BinaryCodec.toAsciiBytes(INITIAL_TEXT.getBytes()));
               Constants.CODEC_TEXT_AREA.setText(FINAL_TEXT);
            } else if(SELECTED_ITEM.equalsIgnoreCase("Binary => PlainText")) {
               final String FINAL_TEXT = new String(BinaryCodec.fromAscii(INITIAL_TEXT.getBytes()));
               Constants.CODEC_TEXT_AREA.setText(FINAL_TEXT);
            } // END if/else
         } // END actionPerformed
      };
   } // END createCodecEncodeDecodeActionListener

   /**
    * createSearchPathBrowseActionListener method<br/>
    * This method defines and returns a new action listener which displays the file chooser of where files can be found to search.<br/>
    * When the user clicks &quot;Browse&quot; from the &quot;Search&quot; dialog, this method is invoked.
    * @return ActionListener The object which handles the file chooser from the Search dialog
    * @see BtnFactory#createSearchPathBrowseBtn()
    */
   public static ActionListener createSearchPathBrowseActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            // Show the file chooser.
            Constants.FILE_CHOOSER.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);

            // Get the user option.
            final int OPTION = Constants.FILE_CHOOSER.showOpenDialog((Component)ae.getSource());

            // Only proceed if the user chose a file/directory.
            if(OPTION == JFileChooser.APPROVE_OPTION) {
               // Set the search path to the file/directory which the user chose.
               Constants.SEARCH_PATH_FIELD.setText(Constants.FILE_CHOOSER.getSelectedFile().getAbsolutePath());
            } // END if
         } // END actionPerformed
      };
   } // END createSearchPathBrowseActionListener

   /**
    * This class field is used to decide if the createSearchSearchActionListener(JDialog, JButton) should continue to loop (search) through files.
    */
   private static boolean breakSearch = false;
   /**
    * createSearchSearchActionListener method<br/>
    * This method defines and returns a new action listener which searches file contents for search terms.<br/>
    * When the user clicks &quot;Search&quot; from the &quot;Search&quot; dialog, this method is invoked.
    * @param DIALOG The dialog to close after the search operation is finished
    * @param BTN The search button which is disabled upon click, and then re-enabled after the search operation is finished
    * @return ActionListener The object which handles the activity of searching for terms in files
    * @see BtnFactory#createSearchSearchBtn(JDialog)
    */
   public static ActionListener createSearchSearchActionListener(final JDialog DIALOG, final JButton BTN) {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            final File FILE = new File(Constants.SEARCH_PATH_FIELD.getText());
            final String SEARCH_TERM = Constants.SEARCH_TERM_FIELD.getText();
            final boolean CASE_SENSITIVE = Constants.CASE_SENSITIVE_CHECK_BOX.isSelected();
            final boolean REGULAR_EXPRESSION = Constants.REGULAR_EXPRESSION_CHECK_BOX.isSelected();
            final java.util.List<String> FILE_LIST = Arrays.asList(Constants.SEARCH_FILE_LIST_FIELD.getText().split("\\s"));

            // Create status pane
            Util.addNewTab(Constants.SEARCH_STATUS_TEXT);
            final DCTextEditor STATUS_PANE = Util.getSelectedTextPane();

            // Interrogate the search path for validity.
            if(Constants.SEARCH_PATH_FIELD.getText().isEmpty()) {
               STATUS_PANE.setText("The search path field is empty.");
            } else if(!FILE.isFile() && !FILE.isDirectory()) {
               STATUS_PANE.setText("The search path is neither a file nor a directory.");
            } else if(Constants.SEARCH_TERM_FIELD.getText().isEmpty()) {
               STATUS_PANE.setText("The search term field is empty.");
            } else {
               // Create a swing worker thread to separate event dispatch work from file process work
               new SwingWorker<Integer,Integer>() {
                  final Collection<File> ALL_FILES = FileUtils.listFiles(FILE, null, Constants.RECURSIVE_SEARCH_CHECK_BOX.isSelected());
                  final Collection<SearchTermOccurrence> OCCURRENCES = new ArrayList<SearchTermOccurrence>();

                  @Override
                  public Integer doInBackground() {
                     SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                           // Set the search progress bar upper bound to the number of files found in the search directory
                           Constants.SEARCH_PROGRESS_BAR.setMaximum(ALL_FILES.size());

                           // Disable search button
                           BTN.setEnabled(false);
                        } // END run
                     });

                     breakSearch = false;

                     // Loop through all files
                     for(final File ONE_FILE : ALL_FILES) {
                        if(breakSearch) break;

                        boolean excludeFilesInList = Constants.SEARCH_FILE_LIST_EXCLUDE_BTN.isSelected();
                        boolean includeFilesInList = Constants.SEARCH_FILE_LIST_INCLUDE_BTN.isSelected();
                        String absPath = ONE_FILE.getAbsolutePath();
                        String extension = FilenameUtils.getExtension(absPath); // returns file extension without the period
                        LineNumberReader fileInput = null;
                        String fileLine;

                        // Only consider this file if it has an extension
                        if(!extension.isEmpty()) {
                           // Is this file's extension within our file list?
                           if(FILE_LIST.contains(Constants.PERIOD + extension)) {
                              if(excludeFilesInList) {
                                 // Skip this file.
                                 // Go to the beginning of the loop.
                                 continue;
                              } // END if
                           } else {
                              if(includeFilesInList) {
                                 // Skip this file.
                                 // Go to the beginning of the loop.
                                 continue;
                              } // END if
                           } // END if/else
                        } // END if

                        try {
                           Util.appendTextLater(STATUS_PANE, String.format("Searching file: %s%s%n", absPath, Constants.ELLIPSIS));

                           // Create a reader for the file
                           fileInput = new LineNumberReader(new FileReader(ONE_FILE));

                           while(null != (fileLine = fileInput.readLine())) {
                              // Search by regular expression
                              if(REGULAR_EXPRESSION) {
                                 if(SEARCH_TERM.contains("^") || SEARCH_TERM.contains("$")) {
                                    if(fileLine.matches(SEARCH_TERM)) {
                                       // Create a new search term occurrence object
                                       final SearchTermOccurrence STO = new SearchTermOccurrence(absPath,fileLine,fileInput.getLineNumber());
                                       Util.appendTextLater(STATUS_PANE, STO.toString());
                                       OCCURRENCES.add(STO);
                                      } // END if
                                 } else if(fileLine.matches(".*" + SEARCH_TERM + ".*")) {
                                    // Create a new search term occurrence object
                                    final SearchTermOccurrence STO = new SearchTermOccurrence(absPath,fileLine,fileInput.getLineNumber());
                                    Util.appendTextLater(STATUS_PANE, STO.toString());
                                    OCCURRENCES.add(STO);
                                 } // END if/else
                              // Search case sensitive
                              } else if(CASE_SENSITIVE) {
                                 if(fileLine.contains(SEARCH_TERM)) {
                                    // Create a new search term occurrence object
                                    final SearchTermOccurrence STO = new SearchTermOccurrence(absPath,fileLine,fileInput.getLineNumber());
                                    Util.appendTextLater(STATUS_PANE, STO.toString());
                                    OCCURRENCES.add(STO);
                                 } // END if
                              // Search case non-sensitive
                              } else if(fileLine.toLowerCase().contains(SEARCH_TERM.toLowerCase())) {
                                 // Create a new search term occurrence object
                                 final SearchTermOccurrence STO = new SearchTermOccurrence(absPath,fileLine,fileInput.getLineNumber());
                                 Util.appendTextLater(STATUS_PANE, STO.toString());
                                 OCCURRENCES.add(STO);
                              } // END if/else
                           } // END file input loop
                        } catch(FileNotFoundException fnfe) {
                           // Create a new message
                           final String MSG = String.format("Error:  File Not Found Error: %s%n", fnfe.getMessage());
                           Util.appendTextLater(STATUS_PANE, MSG);
                        } catch(IOException ioe) {
                           // Create a new message
                           final String MSG = String.format("Error:  File Read Error: %s%n", ioe.getMessage());
                           Util.appendTextLater(STATUS_PANE, MSG);
                        } catch(Exception unknownExc) {
                           // Create a new message
                           final String MSG = String.format("Error:  Unknown Error: %s%n", unknownExc.getMessage());
                           Util.appendTextLater(STATUS_PANE, MSG);
                        } finally {
                           try {
                              fileInput.close();
                           } catch(Exception fileCloseExc) {
                              // Create a new message
                              final String MSG = String.format("Error:  File Close Error: %s%n", fileCloseExc.getMessage());
                              Util.appendTextLater(STATUS_PANE, MSG);
                           } // END try/catch
                        } // END try/catch/finally

                        try {
                           SwingUtilities.invokeAndWait(new Runnable() {
                              public void run() {
                                 // Increment progress bar
                                 Constants.SEARCH_PROGRESS_BAR.setValue(Constants.SEARCH_PROGRESS_BAR.getValue() + 1);
                              } // END run
                           });
                        } catch(Exception e) {}
                     } // END file loop

                     STATUS_PANE.getTextArea().discardAllEdits();

                     // Must return an integer
                     return 0;
                  } // END doInBackground

                  @Override
                  protected void done() {
                     // Create results pane
                     Util.addNewTab(Constants.SEARCH_RESULTS_TEXT);
                     final DCTextEditor RESULTS_PANE = Util.getSelectedTextPane();

                     // Display the number of occurrences
                     final String MSG = String.format("Number of occurrences: %d%n", OCCURRENCES.size());
                     RESULTS_PANE.appendText(MSG);

                     // Loop through all occurrences
                     for(final SearchTermOccurrence STO : OCCURRENCES) {
                        // Display the details of an occurrence
                        RESULTS_PANE.appendText(STO.toString());
                     } // END loop

                     // Re-enable search button
                     BTN.setEnabled(true);

                     // Close the dialog
                     DIALOG.setVisible(false);

                     // Searching is finished, calibrate
                     Constants.SEARCH_PROGRESS_BAR.setValue(0);
                  } // END done
               }.execute();
            } // END if/else
         } // END actionPerformed
      };
   } // END createSearchSearchActionListener

   /**
    * createJavaDocSourceDirectoryActionListener method<br/>
    * This method defines and returns a new action listener which displays the file chooser of where source files can be found.<br/>
    * When the user clicks the top &quot;Browse&quot; button from the &quot;Generate JavaDoc&quot; dialog, this method is invoked. 
    * @return ActionListener The object which handles the directory selection of where source files can be found
    * @see BtnFactory#createJavaDocSourceDirectoryBrowseBtn()
    */
   public static ActionListener createJavaDocSourceDirectoryActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            Constants.FILE_CHOOSER.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
            final int OPTION = Constants.FILE_CHOOSER.showOpenDialog((Component)ae.getSource());

            // Only proceed if the user approved a file.
            if(OPTION == JFileChooser.APPROVE_OPTION) {
               Constants.JAVADOC_SOURCE_FIELD.setText(Constants.FILE_CHOOSER.getSelectedFile().getAbsolutePath());
            } // END if
         } // END actionPerformed
      };
   } // END createJavaDocSourceDirectoryActionListener

   /**
    * createJavaDocDestinationDirectoryActionListener method<br/>
    * This method defines and returns a new action listener which displays the file chooser of where javadocs are to be stored.<br/>
    * When the user clicks the bottom &quot;Browse&quot; button from the &quot;Generate JavaDoc&quot; dialog, this method is invoked.
    * @return ActionListener The object which handles the directory selection of where to store generated javadocs
    * @see BtnFactory#createJavaDocDistinationDirectoryBrowseBtn()
    */
   public static ActionListener createJavaDocDestinationDirectoryActionListener() {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            Constants.FILE_CHOOSER.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
            final int OPTION = Constants.FILE_CHOOSER.showOpenDialog((Component)ae.getSource());

            // Only proceed if the user approved a file.
            if(OPTION == JFileChooser.APPROVE_OPTION) {
               Constants.JAVADOC_DESTINATION_FIELD.setText(Constants.FILE_CHOOSER.getSelectedFile().getAbsolutePath());
            } // END if
         } // END actionPerformed
      };
   } // END createJavaDocDestinationDirectoryActionListener

   /**
    * createGenerateJavaDocActionListener method<br/>
    * This method defines and returns a new action listener which generates javadocs.<br/>
    * When the user clicks the &quot;Gen Java Doc&quot; button from the &quot;Generate JavaDoc&quot; dialog, this method is invoked.
    * @param STYLE_TREE A tree containing CSS data for styling the javadocs.
    * @return ActionListener The object which handles the generation of javadocs
    * @see BtnFactory#createJavaDocGenBtn(JTree)
    */
   public static ActionListener createGenerateJavaDocActionListener(final JTree STYLE_TREE) {
      return new ActionListener() {
         public void actionPerformed(ActionEvent ae) {
            if(null != STYLE_TREE.getModel() && null != STYLE_TREE.getModel().getRoot()) {
               try {
                  // Save any remaining edits.
                  Object currentSelectedItem = STYLE_TREE.getLastSelectedPathComponent();

                  // If this is a java doc node, bring the style definition into the text pane.
                  if(null != currentSelectedItem && currentSelectedItem instanceof JavaDocStyleRuleNode) {
                     JavaDocStyleRuleNode selectedTreeNode = (JavaDocStyleRuleNode)currentSelectedItem;
                     selectedTreeNode.setStyleRuleDefinition(Constants.JAVADOC_STYLE_TEXT_PANE.getText());
                  } // END if

                  // Make sure directories exist before proceeding.
                  if(!new File(Constants.JAVADOC_SOURCE_FIELD.getText()).exists()) {
                     JOptionPane.showMessageDialog(null, Constants.JAVADOC_SOURCE_FIELD.getText() + " does not exist.", "Error", JOptionPane.ERROR_MESSAGE);
                     return;
                  } // END if
                  if(!new File(Constants.JAVADOC_DESTINATION_FIELD.getText()).exists()) {
                     JOptionPane.showMessageDialog(null, Constants.JAVADOC_DESTINATION_FIELD.getText() + " does not exist.", "Error", JOptionPane.ERROR_MESSAGE);
                     return;
                  } // END if

                  // Create the CSS file.
                  String cssRules = Util.getCSSFromTreeModel(STYLE_TREE.getModel(), STYLE_TREE.getModel().getRoot());
                  String cssFile = Constants.JAVADOC_DESTINATION_FIELD.getText();

                  if(!cssFile.endsWith(File.separator)) cssFile += File.separator;
                  cssFile += "DCEdit-api.css";
                  FileWriter cssWriter = new FileWriter(cssFile);
                  cssWriter.write(cssRules);
                  cssWriter.flush();
                  cssWriter.close();

                  String packages = "";
                  File srcDir = new File(Constants.JAVADOC_SOURCE_FIELD.getText());
                  for(String dirName : srcDir.list()) {
                     if(!packages.isEmpty()) packages += ":";
                     packages += dirName;
                  } // END loop

                  // Create the JavaDoc.
                  String[] args = {
                     "-subpackages",  packages,
                     "-doclet",       "dcedit.doc.DefaultDoclet",
                     "-sourcepath",   Constants.JAVADOC_SOURCE_FIELD.getText(),
                     "-d",            Constants.JAVADOC_DESTINATION_FIELD.getText(),
                     "-" +            (String)Constants.VISIBILITY_LEVEL_LIST.getSelectedItem(),
                     "-source",       (String)Constants.SOURCE_COMPLIANCE_LIST.getSelectedItem()
                  };

                  // Run the JavaDoc command.
                  com.sun.tools.javadoc.Main.execute(args);

                  JOptionPane.showMessageDialog(null, "Finished creating javadocs.", "Success", JOptionPane.INFORMATION_MESSAGE);
               } catch(Exception e) {
                  JOptionPane.showMessageDialog(null, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
               } // END try/catch
            } // END if
         } // END actionPerformed
      };
   } // END createGenerateJavaDocActionListener

   /**
    * createJavaDocStyleTreeSelectionListener method<br/>
    * This method defines and returns a new tree selection listener which handles the loading of CSS rules (declared in the tree) into the text box.<br/>
    * This method will also store any edited CSS rules from the text box into the tree.
    * @param STYLE_TREE A tree containing CSS data for styling the javadocs.
    * @return TreeSelectionListener The object which handles the tree-to-text box relationship
    */
   public static TreeSelectionListener createJavaDocStyleTreeSelectionListener(final JTree STYLE_TREE) {
      return new TreeSelectionListener() {
         /** Previous tree node item reference */
         private Object previousItem = null;

         /** valueChanged method */
         public void valueChanged(TreeSelectionEvent tse) {
            Object currentSelectedItem = STYLE_TREE.getLastSelectedPathComponent();

            // If the previous node was a java doc node, save the current style definition from the text pane.
            if(null != previousItem && previousItem instanceof JavaDocStyleRuleNode) {
               JavaDocStyleRuleNode previousTreeNode = (JavaDocStyleRuleNode)previousItem;
               previousTreeNode.setStyleRuleDefinition(Constants.JAVADOC_STYLE_TEXT_PANE.getText());
            } // END if

            // If this is a java doc node, bring the style definition into the text pane.
            if(null != currentSelectedItem && currentSelectedItem instanceof JavaDocStyleRuleNode) {
               Constants.JAVADOC_STYLE_TEXT_PANE.setEnabled(true);
               JavaDocStyleRuleNode selectedTreeNode = (JavaDocStyleRuleNode)currentSelectedItem;

               // Update the text pane with the new style text.
               Constants.JAVADOC_STYLE_TEXT_PANE.setText(selectedTreeNode.getStyleRuleDefinition());
            } else {
               Constants.JAVADOC_STYLE_TEXT_PANE.setEnabled(false);
               Constants.JAVADOC_STYLE_TEXT_PANE.setText("");
            } // END if/else

            // Update the previousItem reference.
            previousItem = currentSelectedItem;
         } // END valueChanged
      };
   } // END createJavaDocStyleTreeSelectionListener
} // END class ActionFactory
